from functools import reduce

# and or感知器的简单实现
class Perceptron(object):
    def __init__(self, input_num, activator):
        '''
        初始化感知器，设置输入参数个数及激活函数
        :param input_num:
        :param activator:
        '''
        self.activator = activator
        # 权重向量初始为0
        self.weights = [0.0 for _ in range(input_num)]
        # 偏置项初始为0
        self.bias = 0.0

    def __str__(self):
        '''
        打印学习到的权重，偏置项
        :return:
        '''
        return 'weights\t:%s\nnbias\t:%f\n' % (self.weights, self.bias)

    def predict(self, input_vec):
        '''
        输入向量，输出感知器的计算结果
        :param input_vec:
        :return:
        '''
        '''
        这里用高阶函数实现传入向量与权值向量相乘加上偏置的操作
        zip(input_vec,self.weights) 
        zip表示将这两个向量(列表)的数对应组合成元组再构成新的列表
        map(lambda xw:xw[0]*xw[1],
                       zip(input_vec,self.weights))
        高阶函数map表示将zip打包的元组列表依次取出相乘(lambda表达式表示)
        reduce(lambda  a,b:a+b,map...zip...,0.0)
        高阶函数reduce表示将map中每一个元组的乘积进行累加(lambda表达式表示)
        第三个参数为初始值，即如果有这个参数，那么第一个操作的元素就不是列表的第一项
        而是这个初始值了，这里设为0
        最后再加上偏置bias
        '''
        return self.activator(
            reduce(lambda a, b: a + b,
                   list(map(lambda xw: xw[0] * xw[1],
                            zip(input_vec, self.weights)))
                   , 0.0) + self.bias)

    def train(self, input_vecs, labels, iteration, rate):
        '''
        输入训练数据：
            一组向量，每个向量对应的labels(真值)，训练次数，学习率
        :param input_vec:
        :param labels:
        :param iteration:
        :param rate:
        :return:
        '''
        for i in range(iteration):
            self._one_iteration(input_vecs, labels, rate)

    def _one_iteration(self, input_vecs, labels, rate):
        '''
        一次训练
        :param input_vecs:
        :param labels:
        :param rate:
        :return:
        '''
        # 将输入和输出打包，即得到一个新的元组列表，每个元组里面有输入和输出
        samples = zip(input_vecs, labels)
        for (input_vec, label) in samples:
            # 计算输出
            output = self.predict(input_vec)
            # 更新权重
            self._update_weights(input_vec, output, label, rate)

    def _update_weights(self, input_vec, output, label, rate):
        '''
        更新权重
        wi+=del(wi)
        del(wi)=rate*(t-output)*xi
        b+=del(b)
        del(b)=rate*(t-output)
        :param input_vec:
        :param output:
        :param label:
        :param rate:
        :return:
        '''
        delta = label - output
        self.weights = list(map(
            lambda xw: xw[1] + rate * delta * xw[0],
            zip(input_vec, list(self.weights))))
        self.bias += rate * delta


# 激活函数
f = lambda x: 1 if x > 0 else 0

def get_training_dataset():
    '''
    构建训练数据
    :return:
    '''
    input_vecs = [[1, 1], [0, 0], [1, 0], [0, 1]]
    labels = [1, 0, 0, 0]
    return input_vecs, labels

def get_and_training_dataset():
    '''
    基于and真值表构建训练数据
    :return:
    '''
    input_vecs = [[1, 1], [0, 0], [1, 0], [0, 1]]
    labels = [1, 0, 0, 0]
    return input_vecs, labels


def get_or_training_dataset():
    '''
    基于and真值表构建训练数据
    :return:
    '''
    input_vecs = [[1, 1], [0, 0], [1, 0], [0, 1]]
    labels = [1, 0, 1, 1]
    return input_vecs, labels


def train_and_perceptron():
    '''
    训练
    :return:
    '''
    # 创建感知器并训练
    p = Perceptron(2, f)
    input_vecs, labels = get_and_training_dataset()
    p.train(input_vecs, labels, 10, 0.1)
    return p


def train_or_perceptron():
    '''
    训练
    :return:
    '''
    # 创建感知器并训练
    p = Perceptron(2, f)
    input_vecs, labels = get_or_training_dataset()
    p.train(input_vecs, labels, 1000, 0.1)
    return p


if __name__ == '__main__':
    # 训练and感知器
    and_perception = train_and_perceptron()
    # 打印训练获得的权重
    print(and_perception)
    # 测试
    print('1 and 1 = %d' % and_perception.predict([1, 1]))
    print('0 and 0 = %d' % and_perception.predict([0, 0]))
    print('1 and 0 = %d' % and_perception.predict([1, 0]))
    print('0 and 1 = %d' % and_perception.predict([0, 1]))

    or_perception = train_or_perceptron()
    # 打印训练获得的权重
    print(or_perception)
    # 测试
    print('1 and 1 = %d' % or_perception.predict([1, 1]))
    print('0 and 0 = %d' % or_perception.predict([0, 0]))
    print('1 and 0 = %d' % or_perception.predict([1, 0]))
    print('0 and 1 = %d' % or_perception.predict([0, 1]))
